package com.xuecheng.content.service.jobhandler;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import com.xuecheng.api.client.MediaClient;
import com.xuecheng.api.client.SearchClient;
import com.xuecheng.api.config.MultipartSupportConfig;
import com.xuecheng.api.dto.CourseIndex;
import com.xuecheng.api.dto.UploadFileResultDTO;
import com.xuecheng.content.model.dto.CourseBaseInfoDTO;
import com.xuecheng.content.model.dto.CoursePreviewDTO;
import com.xuecheng.content.model.dto.TeachPlanDTO;
import com.xuecheng.content.model.po.CoursePublish;
import com.xuecheng.content.model.po.CourseTeacher;
import com.xuecheng.content.service.ICoursePreviewService;
import com.xuecheng.content.service.ICoursePublishService;
import com.xuecheng.content.service.bloom.CourseBloomFilterInitializer;
import com.xuecheng.message.sdk.constant.MqMessageTypeConstant;
import com.xuecheng.message.sdk.model.po.MqMessage;
import com.xuecheng.message.sdk.service.IMqMessageService;
import com.xuecheng.message.sdk.service.MessageProcessAbstract;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.nio.file.Files;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * @author 勾新杰
 * @version 1.0
 * @description: 课程发布任务
 * @date 2025/2/18 1:04
 */
@Slf4j
@Component
public class CoursePublishTask extends MessageProcessAbstract {

    /**
     * 媒资管理客户端
     */
    private final MediaClient mediaClient;

    /**
     * 搜索服务客户端
     */
    private final SearchClient searchClient;

    /**
     * 课程预览服务
     */
    private final ICoursePreviewService coursePreviewService;

    /**
     * 课程发布服务
     */
    private final ICoursePublishService coursePublishService;

    /**
     * redis客户端
     */
    private final RedisTemplate<String, String> redisTemplate;

    /**
     * 布隆过滤器初始化
     */
    private final CourseBloomFilterInitializer courseBloomFilterInitializer;

    /**
     * 键前缀
     */
    private static final String KEY_PREFIX = "course:";

    /**
     * 键过期时间
     */
    private static final Integer KEY_EXPIRE_MINUTES = 5;

    /**
     * 课程发布任务
     *
     * @param mqMessageService             消息服务
     * @param mediaClient                  媒资管理客户端
     * @param searchClient                 搜索服务客户端
     * @param coursePreviewService         课程预览服务
     * @param coursePublishService         课程发布服务
     * @param redisTemplate                redis客户端
     * @param courseBloomFilterInitializer 布隆过滤器初始化
     */
    @Autowired
    public CoursePublishTask(IMqMessageService mqMessageService,
                             MediaClient mediaClient,
                             SearchClient searchClient,
                             ICoursePreviewService coursePreviewService,
                             ICoursePublishService coursePublishService,
                             RedisTemplate<String, String> redisTemplate, CourseBloomFilterInitializer courseBloomFilterInitializer) {
        super(mqMessageService);
        this.mediaClient = mediaClient;
        this.searchClient = searchClient;
        this.coursePreviewService = coursePreviewService;
        this.coursePublishService = coursePublishService;
        this.redisTemplate = redisTemplate;
        this.courseBloomFilterInitializer = courseBloomFilterInitializer;
    }

    /**
     * 课程发布任务
     *
     * @throws Exception 抛异常
     */
    @XxlJob("CoursePublishJobHandler")
    public void coursePublishJobHandler() throws Exception {
        // 分片参数
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();
        log.debug("shardIndex=" + shardIndex + ",shardTotal=" + shardTotal);
        // 参数:分片序号、分片总数、消息类型、一次最多取到的任务数量、一次任务调度执行的超时时间(分钟)
        int count = Runtime.getRuntime().availableProcessors();
        process(shardIndex, shardTotal, MqMessageTypeConstant.MESSAGE_TYPE_COURSE_PUBLISH, count, 60);
    }

    /**
     * 执行任务
     *
     * @param mqMessage 执行任务内容
     * @return boolean true:处理成功，false处理失败
     */
    @Override
    public boolean execute(MqMessage mqMessage) {
        // 1. 获取消息相关的业务信息
        String businessKey1 = mqMessage.getBusinessKey1();
        long courseId = Integer.parseInt(businessKey1);
        try {
            // 2. 课程静态化
            generateCourseHtml(mqMessage, courseId);
            // 3. 课程索引
            saveCourseIndex(mqMessage, courseId);
            // 4. 课程缓存
            saveCourseCache(mqMessage, courseId);
        } catch (RuntimeException e) {
            return false;
        }
        // 5. 都完成后，更新任务状态为成功
        getMqMessageService().success(mqMessage.getId());
        return true;
    }

    /**
     * 课程静态化
     *
     * @param mqMessage 消息
     * @param courseId  课程id
     */
    public void generateCourseHtml(MqMessage mqMessage, long courseId) {
        log.debug("开始进行课程静态化,课程id:{}", courseId);
        Long id = mqMessage.getId();
        IMqMessageService mqMessageService = this.getMqMessageService();
        // 1. 消息幂等性处理
        if (mqMessageService.getStageOne(id) > 0) {
            log.debug("课程静态化已处理直接返回，课程id:{}", courseId);
            return;
        }
        // 2. 生成课程静态化页面
        MultipartFile file = createHtmlFile(courseId);
        if (file == null) {
            mqMessageService.fail(id, "课程静态化处理出错，生成html文件时出错");
            throw new RuntimeException();
        }
        // 3. 上传到文件系统
        UploadFileResultDTO upload = mediaClient.upload(file, courseId);
        if (upload == null) {
            log.error("课程静态化处理出错，在上传html文件时出错。");
            mqMessageService.fail(id, "课程静态化处理出错，上传html文件时出错");
            throw new RuntimeException();
        }
        // 4. 静态化成功，保存第一阶段状态
        mqMessageService.completedStageOne(id);
    }

    /**
     * 生成html文件
     *
     * @param courseId 课程id
     * @return MultipartFile
     */
    private MultipartFile createHtmlFile(long courseId) {
        try {
            // 1. 获取模板文件并转换为模板
            Configuration configuration = new Configuration(Configuration.getVersion());
            // 用这种方式加载模板文件，即便打成了jar包，也能读取
            configuration.setTemplateLoader(new ClassTemplateLoader(this.getClass().getClassLoader(), "/templates"));
            configuration.setDefaultEncoding("utf-8");
            Template template = configuration.getTemplate("course_template.ftl");
            // 2. 获取课程预览数据作为模型数据
            CoursePreviewDTO preview = coursePreviewService.preview(courseId);
            Map<String, Object> map = new HashMap<>();
            map.put("model", preview);
            // 3. 将模板文件结合模型数据生成html文件
            String code = FreeMarkerTemplateUtils.processTemplateIntoString(template, map);
            File tempFile = Files.createTempFile("course", ".html").toFile();
            Files.write(tempFile.toPath(), code.getBytes());
            // 4. 将html文件转换成MultipartFile
            return MultipartSupportConfig.getMultipartFile(tempFile);
        } catch (Exception e) {
            log.error("课程静态化处理出错，在生成临时html文件的时候出错:{}", e.getMessage());
            return null;
        }
    }

    /**
     * 保存课程索引
     *
     * @param mqMessage 消息
     * @param courseId  课程id
     */
    public void saveCourseIndex(MqMessage mqMessage, long courseId) {
        log.debug("保存课程索引信息,课程id:{}", courseId);
        Long id = mqMessage.getId();
        IMqMessageService mqMessageService = this.getMqMessageService();
        // 1. 消息幂等性处理
        if (mqMessageService.getStageTwo(id) > 0) {
            log.debug("课程索引已处理直接返回，课程id:{}", courseId);
            return;
        }
        // 2. 保存课程索引
        CoursePublish coursePublish = coursePublishService.getById(courseId);
        CourseIndex courseIndex = BeanUtil.copyProperties(coursePublish, CourseIndex.class);
        Boolean save = searchClient.save(courseIndex);
        // 3. 保存失败，就记录任务失败、抛出异常
        if (!save) {
            log.error("课程索引保存失败，课程id:{}", courseId);
            mqMessageService.fail(id, "课程索引保存失败，课程id:{}");
            throw new RuntimeException();
        }
        // 4. 索引保存成功，保存第二阶段状态
        mqMessageService.completedStageTwo(id);
    }

    /**
     * 保存课程缓存
     *
     * @param mqMessage 消息
     * @param courseId  课程id
     */
    public void saveCourseCache(MqMessage mqMessage, long courseId) {
        log.debug("将课程信息缓存至redis,课程id:{}", courseId);
        Long id = mqMessage.getId();
        IMqMessageService mqMessageService = this.getMqMessageService();
        // 1. 消息幂等性处理
        if (mqMessageService.getStageThree(id) > 0) {
            log.debug("课程缓存已处理直接返回，课程id:{}", courseId);
            return;
        }
        // 2. 保存课程缓存到redis，并且往布隆过滤器里新增一个值
        CoursePreviewDTO coursePreviewDTO = coursePublishService.getCoursePublishWhole(courseId);
        String jsonString = JSON.toJSONString(coursePreviewDTO);
        redisTemplate.opsForValue().set(KEY_PREFIX + courseId, jsonString, KEY_EXPIRE_MINUTES, TimeUnit.MINUTES);
        courseBloomFilterInitializer.getCourseBloomFilter().add(courseId);
        // 3. 保存课程缓存成功，保存第三阶段状态
        mqMessageService.completedStageThree(id);
    }
}
